package ru.mcfr.oxygen.util;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.xpath.*;
import java.util.*;

public class FillIDs {

    private static Logger logger = Logger.getLogger(FillIDs.class.getName());

    private ArrayList<String> listNumbers = new ArrayList<String>();
    private ArrayList<String> initialListNumbers = new ArrayList<String>();
    //private Multiset<String> tempNumbers;

    private Set<Element> mainDocumentNodes;

    private String uidAttrName = "id";
    private String maxInitIdValue = "";
    private Comparator<String> sorter = null;

    public List<String> lostElements;

    private void initLostElements() {
        lostElements = new ArrayList<String>();
        lostElements.add("метаданные");
        lostElements.add("редакции");
        lostElements.add("ссылки");
    }

    public void initNodes(String xml) {
        this.mainDocumentNodes = getMainNodes(stringXMLtoDocument(xml));
        this.initialListNumbers = getAllId(this.mainDocumentNodes);
        this.listNumbers = this.initialListNumbers;
        this.maxInitIdValue = getMaxIdValue(this.initialListNumbers);
    }

    public FillIDs() {
        initLostElements();
        this.sorter = new SortByIntegerValue();
    }

    public FillIDs(String xmlSrc) {
        initLostElements();
        initNodes(xmlSrc);
    }

    public String getIdValue() {
        Integer intValue = Integer.valueOf(this.maxInitIdValue) + 1;
        String newId = "" + intValue;
        this.maxInitIdValue = newId;
        this.listNumbers.add(newId);
        return newId;
    }

    public String getIdValueFor(String val) {
        if (!isInHistory(val) & isInInitial(val))//cut and paste block
            return val;
        return getIdValue();
    }

    public String getUIDForValue(String val) {
        //this.tempNumbers.remove(val);
        //if (this.tempNumbers.count(val) > 0){
        //    System.out.println(val);
            return getIdValue();
        //}

        //return val;
    }

    public void unUseId(String id) {
        if (this.listNumbers.contains(id))
            this.listNumbers.remove(id);
    }

    public boolean isInHistory(String val) {
        return this.listNumbers.contains(val);
    }

    public boolean isInInitial(String val) {
        return this.initialListNumbers.contains(val);
    }

    public Document docFromFile(String fileName){
        Document doc = null;
        try {
            doc =  XmlTool.docFromFile(fileName);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return doc;
    }

    public void docToFile(Document doc, String fileName){
        try {
            XmlTool.docToFile(doc, fileName);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    public Document stringXMLtoDocument(String xmlText) {
        try {
            return XmlTool.stringXMLtoDocument(xmlText);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        }
    }

    public String xmlDocumentToString(Document XMLDoc) {
        try {
            if (XMLDoc == null)
                return "is empty doc";
            return XmlTool.xmlDocumentToString(XMLDoc);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return e.getMessage();
        }
    }

    public String xmlNodeToString(Node node) {
        try {
            return XmlTool.xmlNodeToString(node);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        }
    }

    public Element xmlFragmentToDOMElement(String xmlFragment) {
        try {
            return XmlTool.xmlFragmentToDOMElement(xmlFragment);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        }
    }

    public Set<Element> getMainNodes(Document XMLDoc){
        String xq = "/документ//*[not(ancestor-or-self::метаданные) and " +
                "not(ancestor-or-self::редакции) and " +
                "not(ancestor-or-self::ссылки)]";
        return getMainNodes(XMLDoc, xq);
    }

    public Set<Element> getMainNodes(Document XMLDoc, String xq) {
        Set<Element> result = new HashSet<Element>();
        try {
            XPathFactory xpFactory = XPathFactory.newInstance();
            XPath xpath = xpFactory.newXPath();
            XPathExpression query = xpath.compile(xq);
            Object elements = query.evaluate(XMLDoc, XPathConstants.NODESET);

            NodeList nodes = (NodeList) elements;
            for (int i = 0; i < nodes.getLength(); i++)
                result.add((Element) nodes.item(i));

        } catch (NumberFormatException e1) {
            logger.error(e1.getMessage());
        } catch (XPathExpressionException e1) {
            logger.error(e1.getMessage());
        } catch (Exception e1) {
            logger.error(e1.getMessage());
        }
        return result;
    }

    private boolean isNumeric(String value) {
        try {
            Integer.valueOf(value);
            if (this.initialListNumbers.contains(value))
                this.initialListNumbers.remove(value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public ArrayList<String> getAllId(Set<Element> mainNodes) {
        ArrayList<String> localNumbers = new ArrayList<String>();
        for (Element e : mainNodes) {
            String eId = e.getAttribute(uidAttrName);
            if (isNumeric(eId))
                localNumbers.add(eId);
        }
        return localNumbers;
    }

    public ArrayList<String> getAllId(Document XMLDoc) {
        return getAllId(getMainNodes(XMLDoc));
    }

    public ArrayList<String> getAllId(String xmlText) {
        return getAllId(stringXMLtoDocument(xmlText));
    }

    public String getMaxIdValue(ArrayList<String> list) {
        if (!list.isEmpty()) {
            Collections.sort(list, sorter);
            return list.get(list.size() - 1);
        }
        return "0";
    }

    public void fillIDinNodeList(NodeList list) {
        for (int i = 0; i < list.getLength(); i++) {
            String stringIdValue = ((Element) list.item(i)).getAttribute("id");
            if (stringIdValue.isEmpty())
                ((Element) list.item(i)).setAttribute("id", "" + getIdValue());
            else {
                ((Element) list.item(i)).setAttribute("id", "" + getIdValueFor(stringIdValue));
            }
            fillIDinNodeList(list.item(i).getChildNodes());
        }
    }

    public String fillIDinFragment(String xmlFragment) {
        String mainXmlFragment = "";
        Element fragment = xmlFragmentToDOMElement(xmlFragment);
        if (fragment == null)
            return "Ошибка заполнения";
        NodeList childs = fragment.getChildNodes();
        fillIDinNodeList(childs);
        for (int i = 0; i < childs.getLength(); i++)
            mainXmlFragment += xmlNodeToString(childs.item(i));

        return mainXmlFragment;
    }

    private void checkIdInElement(Element e) {
        String id = e.getAttribute(uidAttrName);
        if (!isNumeric(id))
            e.setAttribute(uidAttrName, String.valueOf(getIdValue()));
        else
            e.setAttribute(uidAttrName, String.valueOf(getUIDForValue(id)));
    }

    public Document fillXML(Document XMLDoc) {

        Set<Element> mainNodes = getMainNodes(XMLDoc);

        this.initialListNumbers = getAllId(mainNodes);
        this.maxInitIdValue = getMaxIdValue(this.initialListNumbers);

        for (Element e : mainNodes)
            checkIdInElement(e);
        return XMLDoc;
    }

    public String fillXML(String XMLDocStr) {
        Document XMLDoc = stringXMLtoDocument(XMLDocStr);
        XMLDoc = fillXML(XMLDoc);
        return xmlDocumentToString(XMLDoc);
    }

    public void unUseAllIdFromNode(String xmlStringNode) {
        ArrayList<String> allIdFromNode = getAllId(xmlStringNode);
        for (String id : allIdFromNode)
            unUseId(id);
    }

    //for id type xs:ID
    private class SortByComplexValue implements Comparator<String> {
        public int compare(String s, String s1) {
            Integer a = Integer.valueOf(s.substring(1));
            Integer b = Integer.valueOf(s1.substring(1));
            return a.compareTo(b);
        }
    }

    //for id type xs:string
    private class SortByIntegerValue implements Comparator<String> {
        public int compare(String s, String s1) {
            Integer a = Integer.valueOf(s);
            Integer b = Integer.valueOf(s1);
            return a.compareTo(b);
        }
    }

    public void removeAllId(Document doc){
        Set<Element> idList = getMainNodes(doc);
        for (Element e : idList)
            e.removeAttribute(this.uidAttrName);
    }
}
